package nrpc.client.slst;

import java.lang.reflect.Method;
import java.net.InetSocketAddress;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.TreeMap;

import nrpc.client.serst.ConfiguredServiceStrategy;
import nrpc.client.serst.ServiceNode;
import nrpc.client.serst.ServiceStrategy;

public class ConsistentHashSelectStrategy implements SelectStrategy {

	private TreeMap<Integer, ServiceNode> map = new TreeMap<>();
	private Map<ServiceNode, List<Integer>> sMap = new HashMap<>();
	private ServiceStrategy serviceStrategy;
	
	private int getN(){
		int n = 0;
		while(true){
			n = new Random().nextInt();
			if(!map.containsKey(n)) break;
		}
		return n;
	}
	
	private static int nwrap(int h, int i){
		return h + Integer.MAX_VALUE * 2 * i / 3;
	}
	
	private void add(ServiceNode node, Integer...n){
		for(int i = 0; i<n.length; i++){
			map.put(n[i], node);
		}
		sMap.put(node, List.of(n));
	}
	
	public ConsistentHashSelectStrategy(InetSocketAddress address) {
		this(new ConfiguredServiceStrategy(address));
	}
	
	public ConsistentHashSelectStrategy(ServiceStrategy serviceStrategy) {
		this.serviceStrategy = serviceStrategy;
		this.serviceStrategy.subscribe(
				(addedNode)->{
					synchronized(this){
						int h = getN();
						add(addedNode, nwrap(h, 0), nwrap(h, 1), nwrap(h, 2));
						this.notifyAll();
					}
				}, 	
				(deletedNode)->{
					deleteNode(deletedNode);
				});
//		this.socketWrapper = socketStrategy.getSocketWrappers().get(0);
	}
	
	private static int hash(Method method, List<Object> args){
		int r = method.hashCode() * args.hashCode();
		for(int i = 0; i<args.size(); i++) r += args.get(i).hashCode() * i;
		return r;
	}
	
	@Override
	public Iterator<ServiceNode> getNode(Method method, List<Object> args) {
		int h = hash(method, args);
		return new Iterator<ServiceNode>() {
			ServiceNode n;
			@Override
			public boolean hasNext() {
				return map.size()>0;
			}
			@Override
			public ServiceNode next() {
				n = map.lowerEntry(h).getValue();
				return n;
			}
			@Override
			public void remove() {
				deleteNode(n);
			}
		};
	}

	@Override
	public synchronized void deleteNode(ServiceNode node) {
		var x = sMap.remove(node);
		if(x == null) return;
		x.forEach((i)->{
			map.remove(i);
		});
	}

	@Override
	public void close() {
		this.serviceStrategy.close();
	}

}
